<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4.0													  |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group								|
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,	   |
// | that is bundled with this package in the file LICENSE, and is		|
// | available at through the world-wide-web at						   |
// | http://www.php.net/license/2_02.txt.								 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to		  |
// | license@php.net so we can mail you a copy immediately.			   |
// +----------------------------------------------------------------------+
// | Authors: Adam Daniel <adaniel1@eesus.jnj.com>						|
// |		  Bertrand Mansion <bmansion@mamasam.com>					 |
// +----------------------------------------------------------------------+
//
// $Id: QuickForm.php,v 1.1 2006/09/24 17:04:53 jamiesensei Exp $

require_once('PEAR.php');
require_once('HTML/Common.php');

$GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = 
		array(
			'group'		 =>array('HTML/QuickForm/group.php','HTML_QuickForm_group'),
			'hidden'		=>array('HTML/QuickForm/hidden.php','HTML_QuickForm_hidden'),
			'reset'		 =>array('HTML/QuickForm/reset.php','HTML_QuickForm_reset'),
			'checkbox'	  =>array('HTML/QuickForm/checkbox.php','HTML_QuickForm_checkbox'),
			'file'		  =>array('HTML/QuickForm/file.php','HTML_QuickForm_file'),
			'image'		 =>array('HTML/QuickForm/image.php','HTML_QuickForm_image'),
			'password'	  =>array('HTML/QuickForm/password.php','HTML_QuickForm_password'),
			'radio'		 =>array('HTML/QuickForm/radio.php','HTML_QuickForm_radio'),
			'button'		=>array('HTML/QuickForm/button.php','HTML_QuickForm_button'),
			'submit'		=>array('HTML/QuickForm/submit.php','HTML_QuickForm_submit'),
			'select'		=>array('HTML/QuickForm/select.php','HTML_QuickForm_select'),
			'hiddenselect'  =>array('HTML/QuickForm/hiddenselect.php','HTML_QuickForm_hiddenselect'),
			'text'		  =>array('HTML/QuickForm/text.php','HTML_QuickForm_text'),
			'textarea'	  =>array('HTML/QuickForm/textarea.php','HTML_QuickForm_textarea'),
			'link'		  =>array('HTML/QuickForm/link.php','HTML_QuickForm_link'),
			'advcheckbox'   =>array('HTML/QuickForm/advcheckbox.php','HTML_QuickForm_advcheckbox'),
			'date'		  =>array('HTML/QuickForm/date.php','HTML_QuickForm_date'),
			'static'		=>array('HTML/QuickForm/static.php','HTML_QuickForm_static'),
			'header'		=>array('HTML/QuickForm/header.php', 'HTML_QuickForm_header'),
			'html'		  =>array('HTML/QuickForm/html.php', 'HTML_QuickForm_html'),
			'hierselect'	=>array('HTML/QuickForm/hierselect.php', 'HTML_QuickForm_hierselect'),
			'autocomplete'  =>array('HTML/QuickForm/autocomplete.php', 'HTML_QuickForm_autocomplete'),
			'xbutton'	   =>array('HTML/QuickForm/xbutton.php','HTML_QuickForm_xbutton')
		);

$GLOBALS['_HTML_QuickForm_registered_rules'] = array(
	'required'	  => array('html_quickform_rule_required', 'HTML/QuickForm/Rule/Required.php'),
	'maxlength'	 => array('html_quickform_rule_range',	'HTML/QuickForm/Rule/Range.php'),
	'minlength'	 => array('html_quickform_rule_range',	'HTML/QuickForm/Rule/Range.php'),
	'rangelength'   => array('html_quickform_rule_range',	'HTML/QuickForm/Rule/Range.php'),
	'email'		 => array('html_quickform_rule_email',	'HTML/QuickForm/Rule/Email.php'),
	'regex'		 => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'lettersonly'   => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'alphanumeric'  => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'numeric'	   => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'nopunctuation' => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'nonzero'	   => array('html_quickform_rule_regex',	'HTML/QuickForm/Rule/Regex.php'),
	'callback'	  => array('html_quickform_rule_callback', 'HTML/QuickForm/Rule/Callback.php'),
	'compare'	   => array('html_quickform_rule_compare',  'HTML/QuickForm/Rule/Compare.php')
);

// {{{ error codes

/*
 * Error codes for the QuickForm interface, which will be mapped to textual messages
 * in the QuickForm::errorMessage() function.  If you are to add a new error code, be
 * sure to add the textual messages to the QuickForm::errorMessage() function as well
 */

define('QUICKFORM_OK',					  1);
define('QUICKFORM_ERROR',				  -1);
define('QUICKFORM_INVALID_RULE',		   -2);
define('QUICKFORM_NONEXIST_ELEMENT',	   -3);
define('QUICKFORM_INVALID_FILTER',		 -4);
define('QUICKFORM_UNREGISTERED_ELEMENT',   -5);
define('QUICKFORM_INVALID_ELEMENT_NAME',   -6);
define('QUICKFORM_INVALID_PROCESS',		-7);
define('QUICKFORM_DEPRECATED',			 -8);
define('QUICKFORM_INVALID_DATASOURCE',	 -9);

// }}}

/**
* Create, validate and process HTML forms
*
* @author	  Adam Daniel <adaniel1@eesus.jnj.com>
* @author	  Bertrand Mansion <bmansion@mamasam.com>
* @version	 2.0
* @since	   PHP 4.0.3pl1
*/
class HTML_QuickForm extends HTML_Common {
	// {{{ properties

	/**
	 * Array containing the form fields
	 * @since	 1.0
	 * @var  array
	 * @access   private
	 */
	var $_elements = array();

	/**
	 * Array containing element name to index map
	 * @since	 1.1
	 * @var  array
	 * @access   private
	 */
	var $_elementIndex = array();

	/**
	 * Array containing indexes of duplicate elements
	 * @since	 2.10
	 * @var  array
	 * @access   private
	 */
	var $_duplicateIndex = array();

	/**
	 * Array containing required field IDs
	 * @since	 1.0
	 * @var  array
	 * @access   private
	 */ 
	var $_required = array();

	/**
	 * Prefix message in javascript alert if error
	 * @since	 1.0
	 * @var  string
	 * @access   public
	 */ 
	var $_jsPrefix = 'Invalid information entered.';

	/**
	 * Postfix message in javascript alert if error
	 * @since	 1.0
	 * @var  string
	 * @access   public
	 */ 
	var $_jsPostfix = 'Please correct these fields.';

	/**
	 * Datasource object implementing the informal
	 * datasource protocol
	 * @since	 3.3
	 * @var  object
	 * @access   private
	 */
	var $_datasource;

	/**
	 * Array of default form values
	 * @since	 2.0
	 * @var  array
	 * @access   private
	 */
	var $_defaultValues = array();

	/**
	 * Array of constant form values
	 * @since	 2.0
	 * @var  array
	 * @access   private
	 */
	var $_constantValues = array();

	/**
	 * Array of submitted form values
	 * @since	 1.0
	 * @var  array
	 * @access   private
	 */
	var $_submitValues = array();

	/**
	 * Array of submitted form files
	 * @since	 1.0
	 * @var  integer
	 * @access   public
	 */
	var $_submitFiles = array();

	/**
	 * Value for maxfilesize hidden element if form contains file input
	 * @since	 1.0
	 * @var  integer
	 * @access   public
	 */
	var $_maxFileSize = 1048576; // 1 Mb = 1048576

	/**
	 * Flag to know if all fields are frozen
	 * @since	 1.0
	 * @var  boolean
	 * @access   private
	 */
	var $_freezeAll = false;

	/**
	 * Array containing the form rules
	 * @since	 1.0
	 * @var  array
	 * @access   private
	 */
	var $_rules = array();

	/**
	 * Form rules, global variety
	 * @var	 array
	 * @access  private
	 */
	var $_formRules = array();

	/**
	 * Array containing the validation errors
	 * @since	 1.0
	 * @var  array
	 * @access   private
	 */
	var $_errors = array();

	/**
	 * Note for required fields in the form
	 * @var	   string
	 * @since	 1.0
	 * @access	private
	 */
	var $_requiredNote = '<span style="font-size:80%; color:#ff0000;">*</span><span style="font-size:80%;"> denotes required field</span>';

	/**
	 * Whether the form was submitted
	 * @var	   boolean
	 * @access	private
	 */
	var $_flagSubmitted = false;

	// }}}
	// {{{ constructor

	/**
	 * Class constructor
	 * @param	string	  $formName		  Form's name.
	 * @param	string	  $method			(optional)Form's method defaults to 'POST'
	 * @param	string	  $action			(optional)Form's action
	 * @param	string	  $target			(optional)Form's target defaults to '_self'
	 * @param	mixed	   $attributes		(optional)Extra attributes for <form> tag
	 * @param	bool		$trackSubmit	   (optional)Whether to track if the form was submitted by adding a special hidden field
	 * @access   public
	 */
	function HTML_QuickForm($formName='', $method='post', $action='', $target='', $attributes=null, $trackSubmit = false)
	{
		HTML_Common::HTML_Common($attributes);
		$method = (strtoupper($method) == 'GET') ? 'get' : 'post';
		$action = ($action == '') ? $_SERVER['PHP_SELF'] : $action;
		$target = empty($target) ? array() : array('target' => $target);
		$attributes = array('action'=>$action, 'method'=>$method, 'name'=>$formName, 'id'=>$formName) + $target;
		$this->updateAttributes($attributes);
		if (!$trackSubmit || isset($_REQUEST['_qf__' . $formName])) {
			if (1 == get_magic_quotes_gpc()) {
				$this->_submitValues = $this->_recursiveFilter('stripslashes', 'get' == $method? $_GET: $_POST);
				foreach ($_FILES as $keyFirst => $valFirst) {
					foreach ($valFirst as $keySecond => $valSecond) {
						if ('name' == $keySecond) {
							$this->_submitFiles[$keyFirst][$keySecond] = $this->_recursiveFilter('stripslashes', $valSecond);
						} else {
							$this->_submitFiles[$keyFirst][$keySecond] = $valSecond;
						}
					}
				}
			} else {
				$this->_submitValues = 'get' == $method? $_GET: $_POST;
				$this->_submitFiles  = $_FILES;
			}
			$this->_flagSubmitted = count($this->_submitValues) > 0 || count($this->_submitFiles) > 0;
		}
		if ($trackSubmit) {
			unset($this->_submitValues['_qf__' . $formName]);
			$this->addElement('hidden', '_qf__' . $formName, null);
		}
		if (preg_match('/^([0-9]+)([a-zA-Z]*)$/', ini_get('upload_max_filesize'), $matches)) {
			// see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes
			switch (strtoupper($matches['2'])) {
				case 'G':
					$this->_maxFileSize = $matches['1'] * 1073741824;
					break;
				case 'M':
					$this->_maxFileSize = $matches['1'] * 1048576;
					break;
				case 'K':
					$this->_maxFileSize = $matches['1'] * 1024;
					break;
				default:
					$this->_maxFileSize = $matches['1'];
			}
		}	
	} // end constructor

	// }}}
	// {{{ apiVersion()

	/**
	 * Returns the current API version
	 *
	 * @since	 1.0
	 * @access	public
	 * @return	float
	 */
	function apiVersion()
	{
		return 3.2;
	} // end func apiVersion

	// }}}
	// {{{ registerElementType()

	/**
	 * Registers a new element type
	 *
	 * @param	 string	$typeName   Name of element type
	 * @param	 string	$include	Include path for element type
	 * @param	 string	$className  Element class name
	 * @since	 1.0
	 * @access	public
	 * @return	void
	 */
	function registerElementType($typeName, $include, $className)
	{
		$GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($typeName)] = array($include, $className);
	} // end func registerElementType

	// }}}
	// {{{ registerRule()

	/**
	 * Registers a new validation rule
	 *
	 * @param	 string	$ruleName   Name of validation rule
	 * @param	 string	$type	   Either: 'regex', 'function' or 'rule' for an HTML_QuickForm_Rule object
	 * @param	 string	$data1	  Name of function, regular expression or HTML_QuickForm_Rule classname
	 * @param	 string	$data2	  Object parent of above function or HTML_QuickForm_Rule file path
	 * @since	 1.0
	 * @access	public
	 * @return	void
	 */
	function registerRule($ruleName, $type, $data1, $data2 = null)
	{
		include_once('HTML/QuickForm/RuleRegistry.php');
		$registry =& HTML_QuickForm_RuleRegistry::singleton();
		$registry->registerRule($ruleName, $type, $data1, $data2);
	} // end func registerRule

	// }}}
	// {{{ elementExists()

	/**
	 * Returns true if element is in the form
	 *
	 * @param	 string   $element		 form name of element to check
	 * @since	 1.0
	 * @access	public
	 * @return	boolean
	 */
	function elementExists($element=null)
	{
		return isset($this->_elementIndex[$element]);
	} // end func elementExists

	// }}}
	// {{{ setDatasource()

	/**
	 * Sets a datasource object for this form object
	 *
	 * Datasource default and constant values will feed the QuickForm object if
	 * the datasource implements defaultValues() and constantValues() methods.
	 *
	 * @param	 object   $datasource		  datasource object implementing the informal datasource protocol
	 * @param	 mixed	$defaultsFilter	  string or array of filter(s) to apply to default values
	 * @param	 mixed	$constantsFilter	 string or array of filter(s) to apply to constants values
	 * @since	 3.3
	 * @access	public
	 * @return	void
	 */
	function setDatasource(&$datasource, $defaultsFilter = null, $constantsFilter = null)
	{
		if (is_object($datasource)) {
			$this->_datasource =& $datasource;
			if (is_callable(array($datasource, 'defaultValues'))) {
				$this->setDefaults($datasource->defaultValues($this), $defaultsFilter);
			}
			if (is_callable(array($datasource, 'constantValues'))) {
				$this->setConstants($datasource->constantValues($this), $constantsFilter);
			}
		} else {
			return PEAR::raiseError(null, QUICKFORM_INVALID_DATASOURCE, null, E_USER_WARNING, "Datasource is not an object in QuickForm::setDatasource()", 'HTML_QuickForm_Error', true);
		}
	} // end func setDatasource

	// }}}
	// {{{ setDefaults()

	/**
	 * Initializes default form values
	 *
	 * @param	 array	$defaultValues	   values used to fill the form
	 * @param	 mixed	$filter			  (optional) filter(s) to apply to all default values
	 * @since	 1.0
	 * @access	public
	 * @return	void
	 */
	function setDefaults($defaultValues = null, $filter = null)
	{
		if (is_array($defaultValues)) {
			if (isset($filter)) {
				if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) {
					foreach ($filter as $val) {
						if (!is_callable($val)) {
							return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true);
						} else {
							$defaultValues = $this->_recursiveFilter($val, $defaultValues);
						}
					}
				} elseif (!is_callable($filter)) {
					return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setDefaults()", 'HTML_QuickForm_Error', true);
				} else {
					$defaultValues = $this->_recursiveFilter($filter, $defaultValues);
				}
			}
			$this->_defaultValues = HTML_QuickForm::arrayMerge($this->_defaultValues, $defaultValues);
			foreach (array_keys($this->_elements) as $key) {
				$this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
			}
		}
	} // end func setDefaults

	// }}}
	// {{{ setConstants()

	/**
	 * Initializes constant form values.
	 * These values won't get overridden by POST or GET vars
	 *
	 * @param	 array   $constantValues		values used to fill the form	
	 * @param	 mixed	$filter			  (optional) filter(s) to apply to all default values	
	 *
	 * @since	 2.0
	 * @access	public
	 * @return	void
	 */
	function setConstants($constantValues = null, $filter = null)
	{
		if (is_array($constantValues)) {
			if (isset($filter)) {
				if (is_array($filter) && (2 != count($filter) || !is_callable($filter))) {
					foreach ($filter as $val) {
						if (!is_callable($val)) {
							return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true);
						} else {
							$constantValues = $this->_recursiveFilter($val, $constantValues);
						}
					}
				} elseif (!is_callable($filter)) {
					return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::setConstants()", 'HTML_QuickForm_Error', true);
				} else {
					$constantValues = $this->_recursiveFilter($filter, $constantValues);
				}
			}
			$this->_constantValues = HTML_QuickForm::arrayMerge($this->_constantValues, $constantValues);
			foreach (array_keys($this->_elements) as $key) {
				$this->_elements[$key]->onQuickFormEvent('updateValue', null, $this);
			}
		}
	} // end func setConstants

	// }}}
	// {{{ setMaxFileSize()

	/**
	 * Sets the value of MAX_FILE_SIZE hidden element
	 *
	 * @param	 int	$bytes	Size in bytes
	 * @since	 3.0
	 * @access	public
	 * @return	void
	 */
	function setMaxFileSize($bytes = 0)
	{
		if ($bytes > 0) {
			$this->_maxFileSize = $bytes;
		}
		if (!$this->elementExists('MAX_FILE_SIZE')) {
			$this->addElement('hidden', 'MAX_FILE_SIZE', $this->_maxFileSize);
		} else {
			$el =& $this->getElement('MAX_FILE_SIZE');
			$el->updateAttributes(array('value' => $this->_maxFileSize));
		}
	} // end func setMaxFileSize

	// }}}
	// {{{ getMaxFileSize()

	/**
	 * Returns the value of MAX_FILE_SIZE hidden element
	 *
	 * @since	 3.0
	 * @access	public
	 * @return	int   max file size in bytes
	 */
	function getMaxFileSize()
	{
		return $this->_maxFileSize;
	} // end func getMaxFileSize

	// }}}
	// {{{ &createElement()

	/**
	 * Creates a new form element of the given type.
	 * 
	 * This method accepts variable number of parameters, their 
	 * meaning and count depending on $elementType
	 *
	 * @param	 string	 $elementType	type of element to add (text, textarea, file...)
	 * @since	 1.0
	 * @access	public
	 * @return	object extended class of HTML_element
	 * @throws	HTML_QuickForm_Error
	 */
	function &createElement($elementType)
	{
		$args	=  func_get_args();
		$element =& HTML_QuickForm::_loadElement('createElement', $elementType, array_slice($args, 1));
		return $element;
	} // end func createElement

	// }}}
	// {{{ _loadElement()

	/**
	 * Returns a form element of the given type
	 *
	 * @param	 string   $event   event to send to newly created element ('createElement' or 'addElement')
	 * @param	 string   $type	element type
	 * @param	 array	$args	arguments for event
	 * @since	 2.0
	 * @access	private
	 * @return	object	a new element
	 * @throws	HTML_QuickForm_Error
	 */
	function &_loadElement($event, $type, $args)
	{
		$type = strtolower($type);
		if (!HTML_QuickForm::isTypeRegistered($type)) {
			$error = PEAR::raiseError(null, QUICKFORM_UNREGISTERED_ELEMENT, null, E_USER_WARNING, "Element '$type' does not exist in HTML_QuickForm::_loadElement()", 'HTML_QuickForm_Error', true);
			return $error;
		}
		$className = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][1];
		$includeFile = $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][$type][0];
		include_once($includeFile);
		$elementObject =& new $className();
		for ($i = 0; $i < 5; $i++) {
			if (!isset($args[$i])) {
				$args[$i] = null;
			}
		}
		$err = $elementObject->onQuickFormEvent($event, $args, $this);
		if ($err !== true) {
			return $err;
		}
		return $elementObject;
	} // end func _loadElement

	// }}}
	// {{{ addElement()

	/**
	 * Adds an element into the form
	 * 
	 * If $element is a string representing element type, then this 
	 * method accepts variable number of parameters, their meaning 
	 * and count depending on $element
	 *
	 * @param	mixed	  $element		element object or type of element to add (text, textarea, file...)
	 * @since	1.0
	 * @return   object	 reference to element
	 * @access   public
	 * @throws   HTML_QuickForm_Error
	 */
	function &addElement($element)
	{
		if (is_object($element) && is_subclass_of($element, 'html_quickform_element')) {
		   $elementObject = &$element;
		   $elementObject->onQuickFormEvent('updateValue', null, $this);
		} else {
			$args = func_get_args();
			$elementObject =& $this->_loadElement('addElement', $element, array_slice($args, 1));
			if (PEAR::isError($elementObject)) {
				return $elementObject;
			}
		}
		$elementName = $elementObject->getName();

		// Add the element if it is not an incompatible duplicate
		if (!empty($elementName) && isset($this->_elementIndex[$elementName])) {
			if ($this->_elements[$this->_elementIndex[$elementName]]->getType() ==
				$elementObject->getType()) {
				$this->_elements[] =& $elementObject;
				$elKeys = array_keys($this->_elements);
				$this->_duplicateIndex[$elementName][] = end($elKeys);
			} else {
				$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::addElement()", 'HTML_QuickForm_Error', true);
				return $error;
			}
		} else {
			$this->_elements[] =& $elementObject;
			$elKeys = array_keys($this->_elements);
			$this->_elementIndex[$elementName] = end($elKeys);
		}
		if ($this->_freezeAll) {
			$elementObject->freeze();
		}

		return $elementObject;
	} // end func addElement
	
	// }}}
	// {{{ insertElementBefore()

   /**
	* Inserts a new element right before the other element
	*
	* Warning: it is not possible to check whether the $element is already
	* added to the form, therefore if you want to move the existing form
	* element to a new position, you'll have to use removeElement():
	* $form->insertElementBefore($form->removeElement('foo', false), 'bar');
	*
	* @access   public
	* @since	3.2.4
	* @param	object  HTML_QuickForm_element  Element to insert
	* @param	string  Name of the element before which the new one is inserted
	* @return   object  HTML_QuickForm_element  reference to inserted element
	* @throws   HTML_QuickForm_Error
	*/
	function &insertElementBefore(&$element, $nameAfter)
	{
		if (!empty($this->_duplicateIndex[$nameAfter])) {
			$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, 'Several elements named "' . $nameAfter . '" exist in HTML_QuickForm::insertElementBefore().', 'HTML_QuickForm_Error', true);
			return $error;
		} elseif (!$this->elementExists($nameAfter)) {
			$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$nameAfter' does not exist in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true);
			return $error;
		}
		$elementName = $element->getName();
		$targetIdx   = $this->_elementIndex[$nameAfter];
		$duplicate   = false;
		// Like in addElement(), check that it's not an incompatible duplicate
		if (!empty($elementName) && isset($this->_elementIndex[$elementName])) {
			if ($this->_elements[$this->_elementIndex[$elementName]]->getType() != $element->getType()) {
				$error = PEAR::raiseError(null, QUICKFORM_INVALID_ELEMENT_NAME, null, E_USER_WARNING, "Element '$elementName' already exists in HTML_QuickForm::insertElementBefore()", 'HTML_QuickForm_Error', true);
				return $error;
			}
			$duplicate = true;
		}
		// Move all the elements after added back one place, reindex _elementIndex and/or _duplicateIndex
		$elKeys = array_keys($this->_elements);
		for ($i = end($elKeys); $i >= $targetIdx; $i--) {
			if (isset($this->_elements[$i])) {
				$currentName = $this->_elements[$i]->getName();
				$this->_elements[$i + 1] =& $this->_elements[$i];
				if ($this->_elementIndex[$currentName] == $i) {
					$this->_elementIndex[$currentName] = $i + 1;
				} else {
					$dupIdx = array_search($i, $this->_duplicateIndex[$currentName]);
					$this->_duplicateIndex[$currentName][$dupIdx] = $i + 1;
				}
				unset($this->_elements[$i]);
			}
		}
		// Put the element in place finally
		$this->_elements[$targetIdx] =& $element;
		if (!$duplicate) {
			$this->_elementIndex[$elementName] = $targetIdx;
		} else {
			$this->_duplicateIndex[$elementName][] = $targetIdx;
		}
		$element->onQuickFormEvent('updateValue', null, $this);
		if ($this->_freezeAll) {
			$element->freeze();
		}
		// If not done, the elements will appear in reverse order
		ksort($this->_elements);
		return $element;
	}

	// }}}
	// {{{ addGroup()

	/**
	 * Adds an element group
	 * @param	array	  $elements	   array of elements composing the group
	 * @param	string	 $name		   (optional)group name
	 * @param	string	 $groupLabel	 (optional)group label
	 * @param	string	 $separator	  (optional)string to separate elements
	 * @param	string	 $appendName	 (optional)specify whether the group name should be
	 *									  used in the form element name ex: group[element]
	 * @return   object	 reference to added group of elements
	 * @since	2.8
	 * @access   public
	 * @throws   PEAR_Error
	 */
	function &addGroup($elements, $name=null, $groupLabel='', $separator=null, $appendName = true)
	{
		static $anonGroups = 1;

		if (0 == strlen($name)) {
			$name	   = 'qf_group_' . $anonGroups++;
			$appendName = false;
		}
		$group =& $this->addElement('group', $name, $groupLabel, $elements, $separator, $appendName);
		return $group;
	} // end func addGroup
	
	// }}}
	// {{{ &getElement()

	/**
	 * Returns a reference to the element
	 *
	 * @param	 string	 $element	Element name
	 * @since	 2.0
	 * @access	public
	 * @return	object	 reference to element
	 * @throws	HTML_QuickForm_Error
	 */
	function &getElement($element)
	{
		if (isset($this->_elementIndex[$element])) {
			return $this->_elements[$this->_elementIndex[$element]];
		} else {
			$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElement()", 'HTML_QuickForm_Error', true);
			return $error;
		}
	} // end func getElement

	// }}}
	// {{{ &getElementValue()

	/**
	 * Returns the element's raw value
	 * 
	 * This returns the value as submitted by the form (not filtered) 
	 * or set via setDefaults() or setConstants()
	 *
	 * @param	 string	 $element	Element name
	 * @since	 2.0
	 * @access	public
	 * @return	mixed	 element value
	 * @throws	HTML_QuickForm_Error
	 */
	function &getElementValue($element)
	{
		if (!isset($this->_elementIndex[$element])) {
			$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
			return $error;
		}
		$value = $this->_elements[$this->_elementIndex[$element]]->getValue();
		if (isset($this->_duplicateIndex[$element])) {
			foreach ($this->_duplicateIndex[$element] as $index) {
				if (null !== ($v = $this->_elements[$index]->getValue())) {
					if (is_array($value)) {
						$value[] = $v;
					} else {
						$value = (null === $value)? $v: array($value, $v);
					}
				}
			}
		}
		return $value;
	} // end func getElementValue

	// }}}
	// {{{ getSubmitValue()

	/**
	 * Returns the elements value after submit and filter
	 *
	 * @param	 string	 Element name
	 * @since	 2.0
	 * @access	public
	 * @return	mixed	 submitted element value or null if not set
	 */	
	function getSubmitValue($elementName)
	{
		$value = null;
		if (isset($this->_submitValues[$elementName]) || isset($this->_submitFiles[$elementName])) {
			$value = isset($this->_submitValues[$elementName])? $this->_submitValues[$elementName]: array();
			if (is_array($value) && isset($this->_submitFiles[$elementName])) {
				foreach ($this->_submitFiles[$elementName] as $k => $v) {
					$value = HTML_QuickForm::arrayMerge($value, $this->_reindexFiles($this->_submitFiles[$elementName][$k], $k));
				}
			}

		} elseif ('file' == $this->getElementType($elementName)) {
			return $this->getElementValue($elementName);

		} elseif (false !== ($pos = strpos($elementName, '['))) {
			$base = substr($elementName, 0, $pos);
			$idx  = "['" . str_replace(array(']', '['), array('', "']['"), substr($elementName, $pos + 1, -1)) . "']";
			if (isset($this->_submitValues[$base])) {
				$value = eval("return (isset(\$this->_submitValues['{$base}']{$idx})) ? \$this->_submitValues['{$base}']{$idx} : null;");
			}

			if ((is_array($value) || null === $value) && isset($this->_submitFiles[$base])) {
				$props = array('name', 'type', 'size', 'tmp_name', 'error');
				$code  = "if (!isset(\$this->_submitFiles['{$base}']['name']{$idx})) {\n" .
						 "	return null;\n" .
						 "} else {\n" .
						 "	\$v = array();\n";
				foreach ($props as $prop) {
					$code .= "	\$v = HTML_QuickForm::arrayMerge(\$v, \$this->_reindexFiles(\$this->_submitFiles['{$base}']['{$prop}']{$idx}, '{$prop}'));\n";
				}
				$fileValue = eval($code . "	return \$v;\n}\n");
				if (null !== $fileValue) {
					$value = null === $value? $fileValue: HTML_QuickForm::arrayMerge($value, $fileValue);
				}
			}
		}
		
		// This is only supposed to work for groups with appendName = false
		if (null === $value && 'group' == $this->getElementType($elementName)) {
			$group	=& $this->getElement($elementName);
			$elements =& $group->getElements();
			foreach (array_keys($elements) as $key) {
				$name = $group->getElementName($key);
				// prevent endless recursion in case of radios and such
				if ($name != $elementName) {
					if (null !== ($v = $this->getSubmitValue($name))) {
						$value[$name] = $v;
					}
				}
			}
		}
		return $value;
	} // end func getSubmitValue

	// }}}
	// {{{ _reindexFiles()

   /**
	* A helper function to change the indexes in $_FILES array
	*
	* @param  mixed   Some value from the $_FILES array
	* @param  string  The key from the $_FILES array that should be appended
	* @return array
	*/
	function _reindexFiles($value, $key)
	{
		if (!is_array($value)) {
			return array($key => $value);
		} else {
			$ret = array();
			foreach ($value as $k => $v) {
				$ret[$k] = $this->_reindexFiles($v, $key);
			}
			return $ret;
		}
	}

	// }}}
	// {{{ getElementError()

	/**
	 * Returns error corresponding to validated element
	 *
	 * @param	 string	$element		Name of form element to check
	 * @since	 1.0
	 * @access	public
	 * @return	string	error message corresponding to checked element
	 */
	function getElementError($element)
	{
		if (isset($this->_errors[$element])) {
			return $this->_errors[$element];
		}
	} // end func getElementError
	
	// }}}
	// {{{ setElementError()

	/**
	 * Set error message for a form element
	 *
	 * @param	 string	$element	Name of form element to set error for
	 * @param	 string	$message	Error message, if empty then removes the current error message
	 * @since	 1.0	   
	 * @access	public
	 * @return	void
	 */
	function setElementError($element, $message = null)
	{
		if (!empty($message)) {
			$this->_errors[$element] = $message;
		} else {
			unset($this->_errors[$element]);
		}
	} // end func setElementError
		 
	 // }}}
	 // {{{ getElementType()

	 /**
	  * Returns the type of the given element
	  *
	  * @param	  string	$element	Name of form element
	  * @since	  1.1
	  * @access	 public
	  * @return	 string	Type of the element, false if the element is not found
	  */
	 function getElementType($element)
	 {
		 if (isset($this->_elementIndex[$element])) {
			 return $this->_elements[$this->_elementIndex[$element]]->getType();
		 }
		 return false;
	 } // end func getElementType

	 // }}}
	 // {{{ updateElementAttr()

	/**
	 * Updates Attributes for one or more elements
	 *
	 * @param	  mixed	$elements   Array of element names/objects or string of elements to be updated
	 * @param	  mixed	$attrs	  Array or sting of html attributes
	 * @since	  2.10
	 * @access	 public
	 * @return	 void
	 */
	function updateElementAttr($elements, $attrs)
	{
		if (is_string($elements)) {
			$elements = split('[ ]?,[ ]?', $elements);
		}
		foreach (array_keys($elements) as $key) {
			if (is_object($elements[$key]) && is_a($elements[$key], 'HTML_QuickForm_element')) {
				$elements[$key]->updateAttributes($attrs);
			} elseif (isset($this->_elementIndex[$elements[$key]])) {
				$this->_elements[$this->_elementIndex[$elements[$key]]]->updateAttributes($attrs);
				if (isset($this->_duplicateIndex[$elements[$key]])) {
					foreach ($this->_duplicateIndex[$elements[$key]] as $index) {
						$this->_elements[$index]->updateAttributes($attrs);
					}
				}
			}
		}
	} // end func updateElementAttr

	// }}}
	// {{{ removeElement()

	/**
	 * Removes an element
	 *
	 * The method "unlinks" an element from the form, returning the reference
	 * to the element object. If several elements named $elementName exist, 
	 * it removes the first one, leaving the others intact.
	 * 
	 * @param string	$elementName The element name
	 * @param boolean   $removeRules True if rules for this element are to be removed too					 
	 * @access public
	 * @since 2.0
	 * @return object HTML_QuickForm_element	a reference to the removed element
	 * @throws HTML_QuickForm_Error
	 */
	function &removeElement($elementName, $removeRules = true)
	{
		if (!isset($this->_elementIndex[$elementName])) {
			$error = PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$elementName' does not exist in HTML_QuickForm::removeElement()", 'HTML_QuickForm_Error', true);
			return $error;
		}
		$el =& $this->_elements[$this->_elementIndex[$elementName]];
		unset($this->_elements[$this->_elementIndex[$elementName]]);
		if (empty($this->_duplicateIndex[$elementName])) {
			unset($this->_elementIndex[$elementName]);
		} else {
			$this->_elementIndex[$elementName] = array_shift($this->_duplicateIndex[$elementName]);
		}
		if ($removeRules) {
			unset($this->_rules[$elementName], $this->_errors[$elementName]);
		}
		return $el;
	} // end func removeElement

	// }}}
	// {{{ addRule()

	/**
	 * Adds a validation rule for the given field
	 *
	 * If the element is in fact a group, it will be considered as a whole.
	 * To validate grouped elements as separated entities, 
	 * use addGroupRule instead of addRule.
	 *
	 * @param	string	 $element	   Form element name
	 * @param	string	 $message	   Message to display for invalid data
	 * @param	string	 $type		  Rule type, use getRegisteredRules() to get types
	 * @param	string	 $format		(optional)Required for extra rule data
	 * @param	string	 $validation	(optional)Where to perform validation: "server", "client"
	 * @param	boolean	$reset		 Client-side validation: reset the form element to its original value if there is an error?
	 * @param	boolean	$force		 Force the rule to be applied, even if the target form element does not exist
	 * @since	1.0
	 * @access   public
	 * @throws   HTML_QuickForm_Error
	 */
	function addRule($element, $message, $type, $format=null, $validation='server', $reset = false, $force = false)
	{
		if (!$force) {
			if (!is_array($element) && !$this->elementExists($element)) {
				return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
			} elseif (is_array($element)) {
				foreach ($element as $el) {
					if (!$this->elementExists($el)) {
						return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$el' does not exist in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
					}
				}
			}
		}
		if (false === ($newName = $this->isRuleRegistered($type, true))) {
			return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addRule()", 'HTML_QuickForm_Error', true);
		} elseif (is_string($newName)) {
			$type = $newName;
		}
		if (is_array($element)) {
			$dependent = $element;
			$element   = array_shift($dependent);
		} else {
			$dependent = null;
		}
		if ($type == 'required' || $type == 'uploadedfile') {
			$this->_required[] = $element;
		}
		if (!isset($this->_rules[$element])) {
			$this->_rules[$element] = array();
		}
		if ($validation == 'client') {
			$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
		}
		$this->_rules[$element][] = array(
			'type'		=> $type,
			'format'	  => $format,
			'message'	 => $message,
			'validation'  => $validation,
			'reset'	   => $reset,
			'dependent'   => $dependent
		);
	} // end func addRule

	// }}}
	// {{{ addGroupRule()

	/**
	 * Adds a validation rule for the given group of elements
	 *
	 * Only groups with a name can be assigned a validation rule
	 * Use addGroupRule when you need to validate elements inside the group.
	 * Use addRule if you need to validate the group as a whole. In this case,
	 * the same rule will be applied to all elements in the group.
	 * Use addRule if you need to validate the group against a function.
	 *
	 * @param	string	 $group		 Form group name
	 * @param	mixed	  $arg1		  Array for multiple elements or error message string for one element
	 * @param	string	 $type		  (optional)Rule type use getRegisteredRules() to get types
	 * @param	string	 $format		(optional)Required for extra rule data
	 * @param	int		$howmany	   (optional)How many valid elements should be in the group
	 * @param	string	 $validation	(optional)Where to perform validation: "server", "client"
	 * @param	bool	   $reset		 Client-side: whether to reset the element's value to its original state if validation failed.
	 * @since	2.5
	 * @access   public
	 * @throws   HTML_QuickForm_Error
	 */
	function addGroupRule($group, $arg1, $type='', $format=null, $howmany=0, $validation = 'server', $reset = false)
	{
		if (!$this->elementExists($group)) {
			return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Group '$group' does not exist in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
		}

		$groupObj =& $this->getElement($group);
		if (is_array($arg1)) {
			$required = 0;
			foreach ($arg1 as $elementIndex => $rules) {
				$elementName = $groupObj->getElementName($elementIndex);
				foreach ($rules as $rule) {
					$format = (isset($rule[2])) ? $rule[2] : null;
					$validation = (isset($rule[3]) && 'client' == $rule[3])? 'client': 'server';
					$reset = isset($rule[4]) && $rule[4];
					$type = $rule[1];
					if (false === ($newName = $this->isRuleRegistered($type, true))) {
						return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
					} elseif (is_string($newName)) {
						$type = $newName;
					}

					$this->_rules[$elementName][] = array(
														'type'		=> $type,
														'format'	  => $format, 
														'message'	 => $rule[0],
														'validation'  => $validation,
														'reset'	   => $reset,
														'group'	   => $group);

					if ('required' == $type || 'uploadedfile' == $type) {
						$groupObj->_required[] = $elementName;
						$this->_required[] = $elementName;
						$required++;
					}
					if ('client' == $validation) {
						$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
					}
				}
			}
			if ($required > 0 && count($groupObj->getElements()) == $required) {
				$this->_required[] = $group;
			}
		} elseif (is_string($arg1)) {
			if (false === ($newName = $this->isRuleRegistered($type, true))) {
				return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, "Rule '$type' is not registered in HTML_QuickForm::addGroupRule()", 'HTML_QuickForm_Error', true);
			} elseif (is_string($newName)) {
				$type = $newName;
			}

			// addGroupRule() should also handle <select multiple>
			if (is_a($groupObj, 'html_quickform_group')) {
				// Radios need to be handled differently when required
				if ($type == 'required' && $groupObj->getGroupType() == 'radio') {
					$howmany = ($howmany == 0) ? 1 : $howmany;
				} else {
					$howmany = ($howmany == 0) ? count($groupObj->getElements()) : $howmany;
				}
			}

			$this->_rules[$group][] = array('type'	   => $type,
											'format'	 => $format, 
											'message'	=> $arg1,
											'validation' => $validation,
											'howmany'	=> $howmany,
											'reset'	  => $reset);
			if ($type == 'required') {
				$this->_required[] = $group;
			}
			if ($validation == 'client') {
				$this->updateAttributes(array('onsubmit' => 'try { var myValidator = validate_' . $this->_attributes['id'] . '; } catch(e) { return true; } return myValidator(this);'));
			}
		}
	} // end func addGroupRule

	// }}}
	// {{{ addFormRule()

   /**
	* Adds a global validation rule 
	* 
	* This should be used when for a rule involving several fields or if
	* you want to use some completely custom validation for your form.
	* The rule function/method should return true in case of successful 
	* validation and array('element name' => 'error') when there were errors.
	* 
	* @access   public
	* @param	mixed   Callback, either function name or array(&$object, 'method')
	* @throws   HTML_QuickForm_Error
	*/
	function addFormRule($rule)
	{
		if (!is_callable($rule)) {
			return PEAR::raiseError(null, QUICKFORM_INVALID_RULE, null, E_USER_WARNING, 'Callback function does not exist in HTML_QuickForm::addFormRule()', 'HTML_QuickForm_Error', true);
		}
		$this->_formRules[] = $rule;
	}
	
	// }}}
	// {{{ applyFilter()

	/**
	 * Applies a data filter for the given field(s)
	 *
	 * @param	mixed	 $element	   Form element name or array of such names
	 * @param	mixed	 $filter		Callback, either function name or array(&$object, 'method')
	 * @since	2.0
	 * @access   public
	 */
	function applyFilter($element, $filter)
	{
		if (!is_callable($filter)) {
			return PEAR::raiseError(null, QUICKFORM_INVALID_FILTER, null, E_USER_WARNING, "Callback function does not exist in QuickForm::applyFilter()", 'HTML_QuickForm_Error', true);
		}
		if ($element == '__ALL__') {
			$this->_submitValues = $this->_recursiveFilter($filter, $this->_submitValues);
		} else {
			if (!is_array($element)) {
				$element = array($element);
			}
			foreach ($element as $elName) {
				$value = $this->getSubmitValue($elName);
				if (null !== $value) {
					if (false === strpos($elName, '[')) {
						$this->_submitValues[$elName] = $this->_recursiveFilter($filter, $value);
					} else {
						$idx  = "['" . str_replace(array(']', '['), array('', "']['"), $elName) . "']";
						eval("\$this->_submitValues{$idx} = \$this->_recursiveFilter(\$filter, \$value);");
					}
				}
			}
		}
	} // end func applyFilter

	// }}}
	// {{{ _recursiveFilter()

	/**
	 * Recursively apply a filter function
	 *
	 * @param	 string   $filter	filter to apply
	 * @param	 mixed	$value	 submitted values
	 * @since	 2.0
	 * @access	private
	 * @return	cleaned values
	 */
	function _recursiveFilter($filter, $value)
	{
		if (is_array($value)) {
			$cleanValues = array();
			foreach ($value as $k => $v) {
				$cleanValues[$k] = $this->_recursiveFilter($filter, $v);
			}
			return $cleanValues;
		} else {
			return call_user_func($filter, $value);
		}
	} // end func _recursiveFilter

	// }}}
	// {{{ arrayMerge()

   /**
	* Merges two arrays
	*
	* Merges two array like the PHP function array_merge but recursively.
	* The main difference is that existing keys will not be renumbered
	* if they are integers.
	*
	* @access   puplic
	* @param	array   $a  original array
	* @param	array   $b  array which will be merged into first one
	* @return   array   merged array
	*/
	function arrayMerge($a, $b)
	{
		foreach ($b as $k => $v) {
			if (is_array($v)) {
				if (isset($a[$k]) && !is_array($a[$k])) {
					$a[$k] = $v;
				} else {
					if (!isset($a[$k])) {
						$a[$k] = array();
					}
					$a[$k] = HTML_QuickForm::arrayMerge($a[$k], $v);
				}
			} else {
				$a[$k] = $v;
			}
		}
		return $a;
	} // end func arrayMerge

	// }}}
	// {{{ isTypeRegistered()

	/**
	 * Returns whether or not the form element type is supported
	 *
	 * @param	 string   $type	 Form element type
	 * @since	 1.0
	 * @access	public
	 * @return	boolean
	 */
	function isTypeRegistered($type)
	{
		return isset($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'][strtolower($type)]);
	} // end func isTypeRegistered

	// }}}
	// {{{ getRegisteredTypes()

	/**
	 * Returns an array of registered element types
	 *
	 * @since	 1.0
	 * @access	public
	 * @return	array
	 */
	function getRegisteredTypes()
	{
		return array_keys($GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES']);
	} // end func getRegisteredTypes

	// }}}
	// {{{ isRuleRegistered()

	/**
	 * Returns whether or not the given rule is supported
	 *
	 * @param	 string   $name	Validation rule name
	 * @param	 bool	 Whether to automatically register subclasses of HTML_QuickForm_Rule
	 * @since	 1.0
	 * @access	public
	 * @return	mixed	true if previously registered, false if not, new rule name if auto-registering worked
	 */
	function isRuleRegistered($name, $autoRegister = false)
	{
		if (is_scalar($name) && isset($GLOBALS['_HTML_QuickForm_registered_rules'][$name])) {
			return true;
		} elseif (!$autoRegister) {
			return false;
		}
		// automatically register the rule if requested
		include_once 'HTML/QuickForm/RuleRegistry.php';
		$ruleName = false;
		if (is_object($name) && is_a($name, 'html_quickform_rule')) {
			$ruleName = !empty($name->name)? $name->name: strtolower(get_class($name));
		} elseif (is_string($name) && class_exists($name)) {
			$parent = strtolower($name);
			do {
				if ('html_quickform_rule' == strtolower($parent)) {
					$ruleName = strtolower($name);
					break;
				}
			} while ($parent = get_parent_class($parent));
		}
		if ($ruleName) {
			$registry =& HTML_QuickForm_RuleRegistry::singleton();
			$registry->registerRule($ruleName, null, $name);
		}
		return $ruleName;
	} // end func isRuleRegistered

	// }}}
	// {{{ getRegisteredRules()

	/**
	 * Returns an array of registered validation rules
	 *
	 * @since	 1.0
	 * @access	public
	 * @return	array
	 */
	function getRegisteredRules()
	{
		return array_keys($GLOBALS['_HTML_QuickForm_registered_rules']);
	} // end func getRegisteredRules

	// }}}
	// {{{ isElementRequired()

	/**
	 * Returns whether or not the form element is required
	 *
	 * @param	 string   $element	 Form element name
	 * @since	 1.0
	 * @access	public
	 * @return	boolean
	 */
	function isElementRequired($element)
	{
		return in_array($element, $this->_required, true);
	} // end func isElementRequired

	// }}}
	// {{{ isElementFrozen()

	/**
	 * Returns whether or not the form element is frozen
	 *
	 * @param	 string   $element	 Form element name
	 * @since	 1.0
	 * @access	public
	 * @return	boolean
	 */
	function isElementFrozen($element)
	{
		 if (isset($this->_elementIndex[$element])) {
			 return $this->_elements[$this->_elementIndex[$element]]->isFrozen();
		 }
		 return false;
	} // end func isElementFrozen

	// }}}
	// {{{ setJsWarnings()

	/**
	 * Sets JavaScript warning messages
	 *
	 * @param	 string   $pref		Prefix warning
	 * @param	 string   $post		Postfix warning
	 * @since	 1.1
	 * @access	public
	 * @return	void
	 */
	function setJsWarnings($pref, $post)
	{
		$this->_jsPrefix = $pref;
		$this->_jsPostfix = $post;
	} // end func setJsWarnings
	
	// }}}
	// {{{ setRequiredNote()

	/**
	 * Sets required-note
	 *
	 * @param	 string   $note		Message indicating some elements are required
	 * @since	 1.1
	 * @access	public
	 * @return	void
	 */
	function setRequiredNote($note)
	{
		$this->_requiredNote = $note;
	} // end func setRequiredNote

	// }}}
	// {{{ getRequiredNote()

	/**
	 * Returns the required note
	 *
	 * @since	 2.0
	 * @access	public
	 * @return	string
	 */
	function getRequiredNote()
	{
		return $this->_requiredNote;
	} // end func getRequiredNote

	// }}}
	// {{{ validate()

	/**
	 * Performs the server side validation
	 * @access	public
	 * @since	 1.0
	 * @return	boolean   true if no error found
	 */
	function validate()
	{
		if (count($this->_rules) == 0 && count($this->_formRules) == 0 && 
			$this->isSubmitted()) {
			return (0 == count($this->_errors));
		} elseif (!$this->isSubmitted()) {
			return false;
		}

		include_once('HTML/QuickForm/RuleRegistry.php');
		$registry =& HTML_QuickForm_RuleRegistry::singleton();

		foreach ($this->_rules as $target => $rules) {
			$submitValue = $this->getSubmitValue($target);

			foreach ($rules as $rule) {
				if ((isset($rule['group']) && isset($this->_errors[$rule['group']])) ||
					 isset($this->_errors[$target])) {
					continue 2;
				}
				// If element is not required and is empty, we shouldn't validate it
				if (!$this->isElementRequired($target)) {
					if (!isset($submitValue) || '' == $submitValue) {
						continue 2;
					// Fix for bug #3501: we shouldn't validate not uploaded files, either.
					// Unfortunately, we can't just use $element->isUploadedFile() since
					// the element in question can be buried in group. Thus this hack.
					} elseif (is_array($submitValue)) {
						if (false === ($pos = strpos($target, '['))) {
							$isUpload = !empty($this->_submitFiles[$target]);
						} else {
							$base = substr($target, 0, $pos);
							$idx  = "['" . str_replace(array(']', '['), array('', "']['"), substr($target, $pos + 1, -1)) . "']";
							eval("\$isUpload = isset(\$this->_submitFiles['{$base}']['name']{$idx});");
						}
						if ($isUpload && (!isset($submitValue['error']) || 0 != $submitValue['error'])) {
							continue 2;
						}
					}
				}
				if (isset($rule['dependent']) && is_array($rule['dependent'])) {
					$values = array($submitValue);
					foreach ($rule['dependent'] as $elName) {
						$values[] = $this->getSubmitValue($elName);
					}
					$result = $registry->validate($rule['type'], $values, $rule['format'], true);
				} elseif (is_array($submitValue) && !isset($rule['howmany'])) {
					$result = $registry->validate($rule['type'], $submitValue, $rule['format'], true);
				} else {
					$result = $registry->validate($rule['type'], $submitValue, $rule['format'], false);
				}

				if (!$result || (!empty($rule['howmany']) && $rule['howmany'] > (int)$result)) {
					if (isset($rule['group'])) {
						$this->_errors[$rule['group']] = $rule['message'];
					} else {
						$this->_errors[$target] = $rule['message'];
					}
				}
			}
		}

		// process the global rules now
		foreach ($this->_formRules as $rule) {
			if (true !== ($res = call_user_func($rule, $this->_submitValues, $this->_submitFiles))) {
				if (is_array($res)) {
					$this->_errors += $res;
				} else {
					return PEAR::raiseError(null, QUICKFORM_ERROR, null, E_USER_WARNING, 'Form rule callback returned invalid value in HTML_QuickForm::validate()', 'HTML_QuickForm_Error', true);
				}
			}
		}

		return (0 == count($this->_errors));
	} // end func validate

	// }}}
	// {{{ freeze()

	/**
	 * Displays elements without HTML input tags
	 *
	 * @param	mixed   $elementList	   array or string of element(s) to be frozen
	 * @since	 1.0
	 * @access   public
	 * @throws   HTML_QuickForm_Error
	 */
	function freeze($elementList=null)
	{
		if (!isset($elementList)) {
			$this->_freezeAll = true;
			$elementList = array();
		} else {
			if (!is_array($elementList)) {
				$elementList = preg_split('/[ ]*,[ ]*/', $elementList);
			}
			$elementList = array_flip($elementList);
		}

		foreach (array_keys($this->_elements) as $key) {
			$name = $this->_elements[$key]->getName();
			if ($this->_freezeAll || isset($elementList[$name])) {
				$this->_elements[$key]->freeze();
				unset($elementList[$name]);
			}
		}

		if (!empty($elementList)) {
			return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Nonexistant element(s): '" . implode("', '", array_keys($elementList)) . "' in HTML_QuickForm::freeze()", 'HTML_QuickForm_Error', true);
		}
		return true;
	} // end func freeze
		
	// }}}
	// {{{ isFrozen()

	/**
	 * Returns whether or not the whole form is frozen
	 *
	 * @since	 3.0
	 * @access	public
	 * @return	boolean
	 */
	function isFrozen()
	{
		 return $this->_freezeAll;
	} // end func isFrozen

	// }}}
	// {{{ process()

	/**
	 * Performs the form data processing
	 *
	 * @param	mixed	 $callback		Callback, either function name or array(&$object, 'method')
	 * @param	bool	  $mergeFiles	  Whether uploaded files should be processed too
	 * @since	1.0
	 * @access   public
	 * @throws   HTML_QuickForm_Error
	 */
	function process($callback, $mergeFiles = true)
	{
		if (!is_callable($callback)) {
			return PEAR::raiseError(null, QUICKFORM_INVALID_PROCESS, null, E_USER_WARNING, "Callback function does not exist in QuickForm::process()", 'HTML_QuickForm_Error', true);
		}
		$values = ($mergeFiles === true) ? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles) : $this->_submitValues;
		return call_user_func($callback, $values);
	} // end func process

	// }}}
	// {{{ accept()

   /**
	* Accepts a renderer
	*
	* @param object	 An HTML_QuickForm_Renderer object
	* @since 3.0
	* @access public
	* @return void
	*/
	function accept(&$renderer)
	{
		$renderer->startForm($this);
		foreach (array_keys($this->_elements) as $key) {
			$element =& $this->_elements[$key];
			$elementName = $element->getName();
			$required	= ($this->isElementRequired($elementName) && !$element->isFrozen());
			$error	   = $this->getElementError($elementName);
			$element->accept($renderer, $required, $error);
		}
		$renderer->finishForm($this);
	} // end func accept

	// }}}
	// {{{ defaultRenderer()

   /**
	* Returns a reference to default renderer object
	*
	* @access public
	* @since 3.0
	* @return object a default renderer object
	*/
	function &defaultRenderer()
	{
		if (!isset($GLOBALS['_HTML_QuickForm_default_renderer'])) {
			include_once('HTML/QuickForm/Renderer/Default.php');
			$GLOBALS['_HTML_QuickForm_default_renderer'] =& new HTML_QuickForm_Renderer_Default();
		}
		return $GLOBALS['_HTML_QuickForm_default_renderer'];
	} // end func defaultRenderer

	// }}}
	// {{{ toHtml ()

	/**
	 * Returns an HTML version of the form
	 *
	 * @param string $in_data (optional) Any extra data to insert right
	 *			   before form is rendered.  Useful when using templates.
	 *
	 * @return   string	 Html version of the form
	 * @since	 1.0
	 * @access   public
	 */
	function toHtml ($in_data = null)
	{
		if (!is_null($in_data)) {
			$this->addElement('html', $in_data);
		}
		$renderer =& $this->defaultRenderer();
		$this->accept($renderer);
		return $renderer->toHtml();
	} // end func toHtml

	// }}}
	// {{{ getValidationScript()

	/**
	 * Returns the client side validation script
	 *
	 * @since	 2.0
	 * @access	public
	 * @return	string	Javascript to perform validation, empty string if no 'client' rules were added
	 */
	function getValidationScript()
	{
		if (empty($this->_rules) || empty($this->_attributes['onsubmit'])) {
			return '';
		}

		include_once('HTML/QuickForm/RuleRegistry.php');
		$registry =& HTML_QuickForm_RuleRegistry::singleton();
		$test = array();
		$js_escape = array(
			"\r"	=> '\r',
			"\n"	=> '\n',
			"\t"	=> '\t',
			"'"	 => "\\'",
			'"'	 => '\"',
			'\\'	=> '\\\\'
		);

		foreach ($this->_rules as $elementName => $rules) {
			foreach ($rules as $rule) {
				if ('client' == $rule['validation']) {
					unset($element);

					$dependent  = isset($rule['dependent']) && is_array($rule['dependent']);
					$rule['message'] = strtr($rule['message'], $js_escape);

					if (isset($rule['group'])) {
						$group	=& $this->getElement($rule['group']);
						// No JavaScript validation for frozen elements
						if ($group->isFrozen()) {
							continue 2;
						}
						$elements =& $group->getElements();
						foreach (array_keys($elements) as $key) {
							if ($elementName == $group->getElementName($key)) {
								$element =& $elements[$key];
								break;
							}
						}
					} elseif ($dependent) {
						$element   =  array();
						$element[] =& $this->getElement($elementName);
						foreach ($rule['dependent'] as $elName) {
							$element[] =& $this->getElement($elName);
						}
					} else {
						$element =& $this->getElement($elementName);
					}
					// No JavaScript validation for frozen elements
					if (is_object($element) && $element->isFrozen()) {
						continue 2;
					} elseif (is_array($element)) {
						foreach (array_keys($element) as $key) {
							if ($element[$key]->isFrozen()) {
								continue 3;
							}
						}
					}

					$test[] = $registry->getValidationScript($element, $elementName, $rule);
				}
			}
		}
		if (count($test) > 0) {
			return
				"\n<script type=\"text/javascript\">\n" .
				"//<![CDATA[\n" . 
				"function validate_" . $this->_attributes['id'] . "(frm) {\n" .
				"  var value = '';\n" .
				"  var errFlag = new Array();\n" .
				"  var _qfGroups = {};\n" .
				"  _qfMsg = '';\n\n" .
				join("\n", $test) .
				"\n  if (_qfMsg != '') {\n" .
				"	_qfMsg = '" . strtr($this->_jsPrefix, $js_escape) . "' + _qfMsg;\n" .
				"	_qfMsg = _qfMsg + '\\n" . strtr($this->_jsPostfix, $js_escape) . "';\n" .
				"	alert(_qfMsg);\n" .
				"	return false;\n" .
				"  }\n" .
				"  return true;\n" .
				"}\n" .
				"//]]>\n" .
				"</script>";
		}
		return '';
	} // end func getValidationScript

	// }}}
	// {{{ getSubmitValues()

	/**
	 * Returns the values submitted by the form
	 *
	 * @since	 2.0
	 * @access	public
	 * @param	 bool	  Whether uploaded files should be returned too
	 * @return	array
	 */
	function getSubmitValues($mergeFiles = false)
	{
		return $mergeFiles? HTML_QuickForm::arrayMerge($this->_submitValues, $this->_submitFiles): $this->_submitValues;
	} // end func getSubmitValues

	// }}}
	// {{{ toArray()

	/**
	 * Returns the form's contents in an array.
	 *
	 * The description of the array structure is in HTML_QuickForm_Renderer_Array docs
	 * 
	 * @since	 2.0
	 * @access	public
	 * @param	 bool	  Whether to collect hidden elements (passed to the Renderer's constructor)
	 * @return	array of form contents
	 */
	function toArray($collectHidden = false)
	{
		include_once 'HTML/QuickForm/Renderer/Array.php';
		$renderer =& new HTML_QuickForm_Renderer_Array($collectHidden);
		$this->accept($renderer);
		return $renderer->toArray();
	 } // end func toArray

	// }}}
	// {{{ exportValue()

	/**
	 * Returns a 'safe' element's value
	 * 
	 * This method first tries to find a cleaned-up submitted value,
	 * it will return a value set by setValue()/setDefaults()/setConstants()
	 * if submitted value does not exist for the given element.
	 *
	 * @param  string   Name of an element
	 * @access public
	 * @return mixed
	 */
	function exportValue($element)
	{
		if (!isset($this->_elementIndex[$element])) {
			return PEAR::raiseError(null, QUICKFORM_NONEXIST_ELEMENT, null, E_USER_WARNING, "Element '$element' does not exist in HTML_QuickForm::getElementValue()", 'HTML_QuickForm_Error', true);
		}
		$value = $this->_elements[$this->_elementIndex[$element]]->exportValue($this->_submitValues, false);
		if (isset($this->_duplicateIndex[$element])) {
			foreach ($this->_duplicateIndex[$element] as $index) {
				if (null !== ($v = $this->_elements[$index]->exportValue($this->_submitValues, false))) {
					if (is_array($value)) {
						$value[] = $v;
					} else {
						$value = (null === $value)? $v: array($value, $v);
					}
				}
			}
		}
		return $value;
	}

	// }}}
	// {{{ exportValues()

	/**
	 * Returns 'safe' elements' values
	 *
	 * Unlike getSubmitValues(), this will return only the values 
	 * corresponding to the elements present in the form.
	 * 
	 * @param   mixed   Array/string of element names, whose values we want. If not set then return all elements.
	 * @access  public
	 * @return  array   An assoc array of elements' values
	 * @throws  HTML_QuickForm_Error
	 */
	function exportValues($elementList = null)
	{
		$values = array();
		if (null === $elementList) {
			// iterate over all elements, calling their exportValue() methods
			foreach (array_keys($this->_elements) as $key) {
				$value = $this->_elements[$key]->exportValue($this->_submitValues, true);
				if (is_array($value)) {
					// This shit throws a bogus warning in PHP 4.3.x
					$values = HTML_QuickForm::arrayMerge($values, $value);
				}
			}
		} else {
			if (!is_array($elementList)) {
				$elementList = array_map('trim', explode(',', $elementList));
			}
			foreach ($elementList as $elementName) {
				$value = $this->exportValue($elementName);
				if (PEAR::isError($value)) {
					return $value;
				}
				$values[$elementName] = $value;
			}
		}
		return $values;
	}

	// }}}
	// {{{ isSubmitted()

   /**
	* Tells whether the form was already submitted
	*
	* This is useful since the _submitFiles and _submitValues arrays
	* may be completely empty after the trackSubmit value is removed.
	*
	* @access public
	* @return bool
	*/
	function isSubmitted()
	{
		return $this->_flagSubmitted;
	}


	// }}}
	// {{{ isError()

	/**
	 * Tell whether a result from a QuickForm method is an error (an instance of HTML_QuickForm_Error)
	 *
	 * @access public
	 * @param mixed	 result code
	 * @return bool	 whether $value is an error
	 */
	function isError($value)
	{
		return (is_object($value) && is_a($value, 'html_quickform_error'));
	} // end func isError

	// }}}
	// {{{ errorMessage()

	/**
	 * Return a textual error message for an QuickForm error code
	 *
	 * @access  public
	 * @param   int	 error code
	 * @return  string  error message
	 */
	function errorMessage($value)
	{
		// make the variable static so that it only has to do the defining on the first call
		static $errorMessages;

		// define the varies error messages
		if (!isset($errorMessages)) {
			$errorMessages = array(
				QUICKFORM_OK					=> 'no error',
				QUICKFORM_ERROR				 => 'unknown error',
				QUICKFORM_INVALID_RULE		  => 'the rule does not exist as a registered rule',
				QUICKFORM_NONEXIST_ELEMENT	  => 'nonexistent html element',
				QUICKFORM_INVALID_FILTER		=> 'invalid filter',
				QUICKFORM_UNREGISTERED_ELEMENT  => 'unregistered element',
				QUICKFORM_INVALID_ELEMENT_NAME  => 'element already exists',
				QUICKFORM_INVALID_PROCESS	   => 'process callback does not exist',
				QUICKFORM_DEPRECATED			=> 'method is deprecated',
				QUICKFORM_INVALID_DATASOURCE	=> 'datasource is not an object'
			);
		}

		// If this is an error object, then grab the corresponding error code
		if (HTML_QuickForm::isError($value)) {
			$value = $value->getCode();
		}

		// return the textual error message corresponding to the code
		return isset($errorMessages[$value]) ? $errorMessages[$value] : $errorMessages[QUICKFORM_ERROR];
	} // end func errorMessage

	// }}}
} // end class HTML_QuickForm

class HTML_QuickForm_Error extends PEAR_Error {

	// {{{ properties

	/**
	* Prefix for all error messages
	* @var string
	*/
	var $error_message_prefix = 'QuickForm Error: ';

	// }}}
	// {{{ constructor

	/**
	* Creates a quickform error object, extending the PEAR_Error class
	*
	* @param int   $code the error code
	* @param int   $mode the reaction to the error, either return, die or trigger/callback
	* @param int   $level intensity of the error (PHP error code)
	* @param mixed $debuginfo any information that can inform user as to nature of the error
	*/
	function HTML_QuickForm_Error($code = QUICKFORM_ERROR, $mode = PEAR_ERROR_RETURN,
						 $level = E_USER_NOTICE, $debuginfo = null)
	{
		if (is_int($code)) {
			$this->PEAR_Error(HTML_QuickForm::errorMessage($code), $code, $mode, $level, $debuginfo);
		} else {
			$this->PEAR_Error("Invalid error code: $code", QUICKFORM_ERROR, $mode, $level, $debuginfo);
		}
	}

	// }}}
} // end class HTML_QuickForm_Error
?>